#ifndef __U_BASE_HPP__
#define __U_BASE_HPP__

/***
  u-base.hpp base functions for libu
  Copyright (C) 2013  Renweu Gao

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ***/

#if defined(_WIN64) || defined(_WIN32)
#define SYSTEM_PATH_SEPARATOR "\\"
#else
#define SYSTEM_PATH_SEPARATOR "/"
#endif

#include <cstring>
#include <cstdarg>
#include <iostream>
#include <sstream>
#include <cstdio>
#include <cassert>
#include <string>

#include "md5.hpp"
#include "keyboard.hpp"

#ifdef __STD_C11__
#include <thread>
#include <chrono>
#endif


namespace u {

    /*represent for 'None'*/
    const static unsigned char N = 0x00;

    /*represent for 'Directory', and/or 'Default'*/
    const static unsigned char D = 0x01;

    /*represent for 'Suffix'*/
    const static unsigned char S = 0x01;

    /*represent for 'File'*/
    const static unsigned char F = 0x02;

    /*represent for 'Terminal', and/or 'Top'*/
    const static unsigned char T = 0x04;

    /*represent for 'Prefix'*/
    const static unsigned char P = 0x04;

    /*represent for 'Open'*/
    const static unsigned char O = 0x08;

    /*represent for 'Left'*/
    const static unsigned char L = 0x00;

    /*represent for 'Right'*/
    const static unsigned char R = 0x01;

    /*represent for flush flag of stream*/
    const static unsigned char FLUSH = 0x10;


    /**
     ** Function  -- clone a string from a given string
     ** Parameters:
     **           -- value: a string to be cloned
     ** Return    -- a copy of @value
     ***/
    char * string_dup(const std::string &value)
    {
        char *ret = NULL;
        if (! value.empty()) {
            ret = new char[value.size()+1];
            strcpy(ret,value.c_str());
        }
        return ret;
    }

    /**
     ** Function  -- free a string, if string is null, nothing will done
     ** Parameters:
     **           -- strings: string to be release
     ***/
    void string_free(char ** strings) {
        if (strings != NULL) {
            if (*strings != NULL) {
                delete [] (*strings);
                (*strings) = NULL;
            }
        }
    }

    /**
     ** Function  -- free a string array, any string will not be deal with
     ** Parameters:
     **           -- strings: string array to be released
     ***/
    void string_vfree(char***strings) {
        if (strings != NULL && (*strings) != NULL) {
            int size = sizeof(strings) / sizeof(strings[0]);
            for (int i=0; i<size; ++i) {
                string_free(strings[i]);
            }
            delete [] (*strings);
            (*strings) = NULL;
        }
    }

    /**
     ** Function  -- remove a given spliter in a string for the given piosition
     ** Parameters:
     **           -- string: string in which the @spliter will be removed
     **           -- spliter: the character will be removed in @string
     **           -- flag: position in which the @spliter will be removed
     **                    u::P for the prefix of @string
     **                    u::S for the suffix of @string
     ** Return    -- the number of @spliter have be removed
     ***/
    size_t string_trim(char *string, unsigned char spliter, unsigned char flag = u::P|u::S) {
        assert(string != NULL);
        int total = 0;
        if ((flag & u::S) == u::S) {
            size_t size = strlen(string);
            while (size > 0 && string[size-1] == spliter) {
                ++ total;
                string[size-1] = '\0';
                -- size;
            }
        }
        if ((flag & u::P) == u::P) {
            size_t size = strlen(string);
            size_t i = 0;
            size_t prefix = 0;
            while (i < size && string[i] == spliter) {
                ++ prefix;
                ++ i;
            }
            i=0;
            total += prefix;
            size_t valiable = size - prefix;
            while (valiable > 0) {
                string[i] = string[i+prefix];
                ++ i;
                -- valiable;
            }
            string[size-prefix] = '\0';
        }
        return total;
    }

    /**
     ** Function  -- remove a given spliter in a const string for the given piosition
     ** Parameters:
     **           -- string: string in which the @spliter will be removed
     **           -- spliter: the character will be removed in @string
     **           -- flag: position in which the @spliter will be removed
     **                    u::P for the prefix of @string
     **                    u::S for the suffix of @string
     ** Return    -- a new string that @spliter has been removed
     ***/
    char *string_trim(const std::string &string, unsigned char spliter, unsigned char flag = u::P|u::S) {
        char *dup = u::string_dup(string);
        if (dup != NULL) {
            size_t total = string_trim(dup, spliter, flag);
            if (total == 0) {
                u::string_free(&dup);
            }
        }
        return dup;
    }

    /**
     ** Function  -- convert string to a given data type
     ** Parameters:
     **           -- value: string data type to be converted
     **           -- ret: a storage to store new data type value of @value
     ** Return    -- true if no error ocurred, otherwise false returned
     ***/
    template<typename T>
        bool from_string(const std::string &value, T &ret) {
            bool bad = true;
            if (! value.empty()) {
                std::istringstream iss(value);
                iss >> ret;
                bad = (iss.fail() || !iss.eof());
            }
            return !bad;
        }

    /**
     ** Function  -- convert a given data type to string    
     ** Parameters:
     **           -- value: data type to be converted
     **           -- ret: a storage to store coverted string if ret is not null
     ** Return    -- true if no error ocurred, otherwise false returned
     ***/
    template<typename T>
        bool to_string(T value, char **ret) {
            std::ostringstream oss;
            oss << value;
            bool bad = oss.fail();
            if (ret != NULL) {
                u::string_free(ret);
                (*ret) = string_dup(oss.str());
            }
            return !bad;
        }

    /**
     ** Function  -- convert a given data type to string
     ** Parameters:
     **           -- value: data type to be converted
     **           -- ret: a storage to store coverted string if ret is not null
     ** Return    -- true if no error ocurred, otherwise false returned
     ***/
    char * va_format(const char *format, va_list arg) {
        char * ret = NULL;
        if (format != NULL) {
            char short_buf[256];
            const int needed = vsnprintf(short_buf, sizeof(short_buf), format, arg);
            if (needed <= 0) {
                std::cerr<<"unknown format in "<<format<<std::endl;
                assert(needed > 0);
            }
            if (needed <= static_cast<int>(sizeof(short_buf))) {
                ret = string_dup(short_buf);
            } else {
                char *buff = new char[needed];
                vsnprintf(buff, needed, format, arg);
                ret = buff;
            }
        }
        return ret;
    }

    char * format(const char *format, ...) {
        char * ret = NULL;
        if (format != NULL) {
            va_list arg_list;
            va_start(arg_list, format);
            ret = u::va_format(format, arg_list);
            va_end(arg_list);
        }
        return ret;
    }

    char *string_lower(const std::string &string) {
        size_t len = string.size();
        std::string ret(string);
        for (size_t i=0; i<len; ++i) {
            if (isalpha(string[i])) {
                ret[i] = static_cast<char>(tolower(string[i]));
            }
        }
        return string_dup(ret);
    }

    char *string_upper(const std::string &string) {
        size_t len = string.size();
        std::string ret(string);
        for (size_t i=0; i<len; ++i) {
            if (isalpha(string[i])) {
                ret[i] = static_cast<char>(toupper(string[i]));
            }
        }
        return string_dup(ret);
    }

    void claim(int expression, const char *format, ...) {
        if (! expression) {
            char *msg = NULL;
            va_list arg_list;
            va_start(arg_list, format);
            msg = u::va_format(format, arg_list);
            va_end(arg_list);
            std::cout << msg << std::endl;
            u::string_free(&msg);
            assert(expression);
        }
    }

    bool string_validate_printable(const std::string &value) {
        assert(value.size()==1);
        return std::isprint(value[0]);
    }

    template <typename T>
        bool string_validate_type(const std::string &value, T* ret = NULL) {
            T ivalue;
            bool result = from_string<T>(value, ivalue);
            if (ret != NULL)
                *ret = ivalue;
            return result;
        }

    bool string_validate_bool(const std::string &value) {
        return (value == "true" || value == "false" || value == "on" || value == "off" || value == "yes" || value == "no");
    }

    bool string_validate_int(const std::string &value) {
        return (!value.empty() && string_validate_type<int>(value));
    }

    bool string_validate_uint(const std::string &value) {
        return (!value.empty() && value[0] != '-' && string_validate_type<unsigned int>(value));
    }

    bool string_validate_short(const std::string &value) {
        return (!value.empty() && string_validate_type<short>(value));
    }

    bool string_validate_ushort(const std::string &value) {
        return (!value.empty() && value[0]!='-' && string_validate_type<unsigned short>(value));
    }

    bool string_validate_long(const std::string &value) {
        return (!value.empty() && string_validate_type<long>(value));
    }

    bool string_validate_ulong(const std::string &value) {
        return (!value.empty() && value[0]!= '-' && string_validate_type<unsigned long>(value));
    }

    bool string_validate_float(const std::string &value) {
        return (!value.empty() && string_validate_type<float>(value));
    }

    bool string_validate_double(const std::string &value) {
        return (!value.empty() && string_validate_type<double>(value));
    }

    bool string_validate_long_double(const std::string &value) {
        return string_validate_type<long double>(value);
    }

    bool string_validate_string(const std::string &value) {
        return true;
    }

#ifdef __STD_C11__
    int wait_key(int millseconds) {
        int key = -1;
        std::thread key_thread([&](){
                key = static_cast<int>(event::keyboard::get());
                });
        key_thread.detach();
        if (millseconds > 0) {
            millseconds *= 10;
            while(key==-1 && (--millseconds) > 0)
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        return key;
    }
#endif

    namespace md5 {

        template <int blocksize=64>
            std::string md5(const std::string& seed) {
                u::md5::MD5<blocksize> md5 = u::md5::MD5<blocksize>(seed);
                return md5.hexdigest();
            }

    }
}

#endif
